package com.rtsapp.server.benchmark.script;

import com.rtsapp.server.common.ByteBuffer;
import com.rtsapp.server.logger.Logger;
import com.rtsapp.server.logger.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;

/**
 * 日志文件分析
 */
public class ScriptAnalyze {

    private static final Logger LOGGER = LoggerFactory.getLogger( ScriptAnalyze.class );

    /**
     * 返回解析成ScriptCommand的迭代器
     * 迭代器线程不安全
     * @param scriptLog
     * @param factory
     * @return
     * @throws IOException
     */
    public Iterator<ScriptCommand> parseLog( String scriptLog, IScriptCommandFactory factory ) throws IOException {
        return new ScriptCommandIterator( scriptLog , factory );
    }


    /**
     * 日志可能很大， 因此采用迭代器的方式一个一个获取
     * 该方法返回的迭代器不是线程安全的
     * @return
     */
    public Iterator<ByteBuffer> parseLog( String scriptLog ) throws IOException {
        File f = new File( scriptLog );
        return new ScriptLogIterator( f );
    }


    /**
     * 脚本解析成顺序命令列表
     * @param logFile 要解析的文件
     * @throws IOException
     */
    public  void  parseList(  String logFile,  IScriptCommandFactory factory,   Set<Integer> extractUserIds, List<ScriptCommand> commandList ) throws IOException {

        Iterator<ScriptCommand> it =  this.parseLog(logFile, factory );

        boolean isExtractAll = (extractUserIds == null) || ( extractUserIds.size() < 1);
        while( it.hasNext() ){
            ScriptCommand command = it.next();

            command = factory.fixCommand(command);

            if( isExtractAll ||  extractUserIds.contains( command.getUserId( ) ) ) {
                commandList.add(command);
            }
        }
    }



    /**
     * 脚本解析
     * @param logFile 要解析的文件
     * @param extractUserIds 要提取的玩家id集合,如果没有设置，默认提取所有
     * @return 解析到的玩家命令, key:玩家id, value:玩家的所有命令列表
     * @throws IOException
     */
    public  void  parse(  String logFile,  IScriptCommandFactory factory,  Set<Integer> extractUserIds, Map<Integer, List<ScriptCommand>> outCommandsMap ) throws IOException {

        Iterator<ScriptCommand> it =  this.parseLog(logFile, factory );

        boolean isExtractAll = (extractUserIds == null) || ( extractUserIds.size() < 1);
        while( it.hasNext() ){
            ScriptCommand command = it.next();

            command = factory.fixCommand(command);

            if( isExtractAll ||  extractUserIds.contains( command.getUserId( ) ) ) {

                appendCommand(outCommandsMap, command);
            }
        }
    }




    private  void appendCommand( Map<Integer, List<ScriptCommand>> commandsMap, ScriptCommand command) {
        getCommandList( commandsMap, command ).add( command );
    }


    private List<ScriptCommand> getCommandList( Map<Integer, List<ScriptCommand>> commandsMap,  ScriptCommand command ){
        List<ScriptCommand> commandList = commandsMap.get( command.getUserId() );

        if( commandList == null ){
            commandList = new ArrayList<>();
            commandsMap.put( command.getUserId(), commandList );
        }

        return commandList;
    }




    /**
     * ScriptCommand迭代器
     */
    private static class ScriptCommandIterator implements Iterator<ScriptCommand>{

        private final ScriptLogIterator logIterator;
        private final IScriptCommandFactory factory;

        public ScriptCommandIterator(String scriptLog,  IScriptCommandFactory commandFactory) throws IOException {

            logIterator = new ScriptLogIterator( new File( scriptLog ) );
            this.factory = commandFactory;

        }

        @Override
        public boolean hasNext() {
            return logIterator.hasNext();
        }

        @Override
        public ScriptCommand next() {
            if( logIterator.hasNext() ){

                ByteBuffer buffer = logIterator.next();

                ScriptCommand command = new ScriptCommand();

                command.readFromBuffer( buffer, factory );

                return command;

            }else {
                return null;
            }
        }
    }


    /**
     * Buffer迭代器
     */
    private static class ScriptLogIterator implements Iterator<ByteBuffer>{

        private final RandomAccessFile randomAccessFile;
        private final long fileLenth;

        private final byte[] bytes = new byte[4];

        private ByteBuffer currentBuffer = null;


        public ScriptLogIterator( File f ) throws IOException {
            randomAccessFile = new RandomAccessFile( f, "r" );
            fileLenth = randomAccessFile.length();

            parseNextBuffer();
        }

        /**
         * 如果当前有ByteBuffer, 返回true
         * 否则，尝试解析出一个ByteBuffer, 成功后返回true
         */
        @Override
        public boolean hasNext() {
            return currentBuffer != null;
        }


        /**
         * 如果没有下一个返回null
         * 否则返回当前的
         * @return
         */
        @Override
        public ByteBuffer next() {

            if( hasNext() ){

                ByteBuffer ret = currentBuffer;

                parseNextBuffer();

                return ret;
            }else{
                return null;
            }
        }


        /**
         * 先置空当前的buffer, 再尝试读取下一个buffer
         */
        private void parseNextBuffer(){

            try {
                currentBuffer = null;

                long curPos  = randomAccessFile.getFilePointer();
                if( curPos + ScriptCommand.HEAD_LENGTH > fileLenth  ){
                    return ;
                }else{

                    // 读取size
                    randomAccessFile.read( bytes, 0, 4 );
                    ByteBuffer buffer = new ByteBuffer( bytes );
                    int size = buffer.readInt() - 4;

                    // size 记录的长度大于
                    if( randomAccessFile.getFilePointer() + size  > fileLenth ){
                        return;
                    }

                    //读取buffer
                    byte bs[] = new byte[size];
                    randomAccessFile.read( bs, 0, size );
                    currentBuffer = new ByteBuffer( bs );
                }
            } catch (IOException e) {
                LOGGER.error( e );
            }
        }
    }

}
